/* Copyright  2009
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/**
  @file my_decimal.h

  It is interface module to fixed precision decimals library.

  Most functions use 'uint32_t mask' as parameter, if during operation error
  which fit in this mask is detected then it will be processed automatically
  here. (errors are E_DEC_* constants, see include/decimal.h)

  Most function are just inline wrappers around library calls
*/

#ifndef __decimal_h__
#define __decimal_h__

#ifdef __cplusplus
extern "C" {
#endif

#include <decimal.h>
#include <collapsar_time.h>
#include <sql_string.h>

#ifdef __cplusplus
}
#endif


#define DECIMAL_LONGLONG_DIGITS 22
#define DECIMAL_LONG_DIGITS     10
#define DECIMAL_LONG3_DIGITS    8

/** maximum length of buffer in our big digits (uint32_t). */
#define DECIMAL_BUFF_LENGTH     9

/* the number of digits that my_decimal can possibly contain */
#define DECIMAL_MAX_POSSIBLE_PRECISION (DECIMAL_BUFF_LENGTH * 9)


/**
  maximum guaranteed precision of number in decimal digits (number of our
  digits * number of decimal digits in one our big digit - number of decimal
  digits in one our big digit decreased by 1 (because we always put decimal
  point on the border of our big digits))
*/
#define DECIMAL_MAX_PRECISION (DECIMAL_MAX_POSSIBLE_PRECISION - 8*2)
#define DECIMAL_MAX_SCALE     30
#define DECIMAL_NOT_SPECIFIED 31

/**
  maximum length of string representation (number of maximum decimal
  digits + 1 position for sign + 1 position for decimal point)
*/
#define DECIMAL_MAX_STR_LENGTH (DECIMAL_MAX_POSSIBLE_PRECISION + 2)

/**
  maximum size of packet length.
*/
#define DECIMAL_MAX_FIELD_SIZE DECIMAL_MAX_PRECISION


inline uint32_t my_decimal_size(uint32_t precision, uint32_t scale)
{
  /*
    Always allocate more space to allow library to put decimal point
    where it want
  */
  return decimal_size(precision, scale) + 1;
}


inline int my_decimal_int_part(uint32_t precision, uint32_t decimals)
{
  return precision - ((decimals == DECIMAL_NOT_SPECIFIED) ? 0 : decimals);
}


/**
  my_decimal class limits 'decimal_t' type to what we need in MySQL.

  It contains internally all necessary space needed by the instance so
  no extra memory is needed. One should call fix_buffer_pointer() function
  when he moves my_decimal objects in memory.
*/

class my_decimal :public decimal_t
{
  decimal_digit_t buffer[DECIMAL_BUFF_LENGTH];

public:

  inline void init()
  {
    len= DECIMAL_BUFF_LENGTH;
    buf= buffer;
#if !defined (HAVE_purify)
    /* Set buffer to 'random' value to find wrong buffer usage */
    for (uint32_t i= 0; i < DECIMAL_BUFF_LENGTH; i++)
      buffer[i]= i;
#endif
  }
  my_decimal()
  {
    init();
  }
  inline void fix_buffer_pointer() { buf= buffer; }

  inline bool sign() const { return decimal_t::sign; }
  inline void sign(bool s) { decimal_t::sign= s; }
  inline uint32_t precision() const { return intg + frac; }
};

typedef my_decimal  db_decimal  ;

int decimal_operation_results(int result);

inline
void max_my_decimal(db_decimal *to, int precision, int frac)
{
  assert((precision <= DECIMAL_MAX_PRECISION)&&
              (frac <= DECIMAL_MAX_SCALE));
  max_decimal(precision, frac, (decimal_t*) to);
}

inline void max_internal_decimal(db_decimal *to)
{
  max_my_decimal(to, DECIMAL_MAX_PRECISION, 0);
}

inline int check_result(uint32_t mask, int result)
{
  if (result & mask)
    decimal_operation_results(result);
  return result;
}

inline int check_result_and_overflow(uint32_t mask, int result, db_decimal *val)
{
  if (check_result(mask, result) & E_DEC_OVERFLOW)
  {
    bool sign= val->sign();
    val->fix_buffer_pointer();
    max_internal_decimal(val);
    val->sign(sign);
  }
  return result;
}

inline uint32_t my_decimal_length_to_precision(uint32_t length, uint32_t scale,
                                           bool unsigned_flag)
{
  return (uint32_t) (length - (scale>0 ? 1:0) - (unsigned_flag ? 0:1));
}

inline uint32_t my_decimal_precision_to_length(uint32_t precision, uint8_t scale,
                                             bool unsigned_flag)
{
  set_if_smaller(precision, (uint32_t)DECIMAL_MAX_PRECISION);
  return (uint32_t)(precision + (scale>0 ? 1:0) + (unsigned_flag ? 0:1));
}

#define macro_decimal_length_to_precision(A,B,C)  my_decimal_length_to_precision(A,B,C)

inline
int my_decimal_string_length(const db_decimal *d)
{
  return decimal_string_size(d);
}


inline
int my_decimal_max_length(const db_decimal *d)
{
  /* -1 because we do not count \0 */
  return decimal_string_size(d) - 1;
}


inline
int my_decimal_get_binary_size(uint32_t precision, uint32_t scale)
{
  return decimal_bin_size((int)precision, (int)scale);
}


inline
void my_decimal2decimal(const db_decimal *from, db_decimal *to)
{
  *to= *from;
  to->fix_buffer_pointer();
}


int my_decimal2binary(uint32_t mask, const db_decimal *d, unsigned char *bin, int prec,
                      int scale);


inline
int binary2my_decimal(uint32_t mask, const unsigned char *bin, db_decimal *d, int prec,
                      int scale)
{
  return check_result(mask, bin2decimal(bin, (decimal_t*) d, prec, scale));
}


inline
int my_decimal_set_zero(db_decimal *d)
{
  decimal_make_zero(((decimal_t*) d));
  return 0;
}


inline
bool my_decimal_is_zero(const db_decimal *decimal_value)
{
  return decimal_is_zero((decimal_t*) decimal_value);
}


inline
int my_decimal_round(uint32_t mask, const db_decimal *from, int scale,
                     bool truncate,db_decimal *to)
{
  return check_result(mask, decimal_round((decimal_t*) from, to, scale,
                                          (truncate ? TRUNCATE : HALF_UP)));
}


inline
int my_decimal_floor(uint32_t mask, const db_decimal *from, db_decimal *to)
{
  return check_result(mask, decimal_round((decimal_t*) from, to, 0, FLOOR));
}


inline
int my_decimal_ceiling(uint32_t mask, const db_decimal *from, db_decimal *to)
{
  return check_result(mask, decimal_round((decimal_t*) from, to, 0, CEILING));
}


int my_decimal2string(uint32_t mask, const db_decimal *d, uint32_t fixed_prec,
                      uint32_t fixed_dec, char filler, String *str);

inline
int my_decimal2int(uint32_t mask, const db_decimal *d, bool unsigned_flag,
                   int64_t *l)
{
  db_decimal rounded;
  /* decimal_round can return only E_DEC_TRUNCATED */
  decimal_round((decimal_t*)d, &rounded, 0, HALF_UP);
  return check_result(mask, (unsigned_flag ?
                 decimal2uint64_t(&rounded, (uint64_t *)l) :
                 decimal2int64_t(&rounded, l)));
}


inline
int my_decimal2double(uint32_t, const db_decimal *d, double *result)
{
  /* No need to call check_result as this will always succeed */
  return decimal2double((decimal_t*) d, result);
}


inline
int str2my_decimal(uint32_t mask, char *str, db_decimal *d, char **end)
{
  return check_result_and_overflow(mask, string2decimal(str,(decimal_t*)d,end),
                                   d);
}


int str2my_decimal(uint32_t mask, const char *from, uint32_t length,
                   const CHARSET_INFO * charset, my_decimal *decimal_value);

inline
int string2my_decimal(uint32_t mask, const String *str, db_decimal *d)
{
  return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), d);
}


db_decimal *date2my_decimal(COLLAPSAR_TIME *ltime, db_decimal *dec);


inline
int double2my_decimal(uint32_t mask, double val, db_decimal *d)
{
  return check_result_and_overflow(mask, double2decimal(val, (decimal_t*)d), d);
}


inline
int int2my_decimal(uint32_t mask, int64_t i, bool unsigned_flag, db_decimal *d)
{
  return check_result(mask, (unsigned_flag ?
                      uint64_t2decimal((uint64_t)i, d) :
                      int64_t2decimal(i, d)));
}


inline
void my_decimal_neg(decimal_t *arg)
{
  if (decimal_is_zero(arg))
  {
    arg->sign= 0;
    return;
  }
  decimal_neg(arg);
}


inline
int my_decimal_add(uint32_t mask, db_decimal *res, const db_decimal *a,
                   const db_decimal *b)
{
  return check_result_and_overflow(mask,
                                   decimal_add((decimal_t*)a,(decimal_t*)b,res),
                                   res);
}


inline
int my_decimal_sub(uint32_t mask, db_decimal *res, const db_decimal *a,
                       const db_decimal *b)
{
  return check_result_and_overflow(mask,
                                   decimal_sub((decimal_t*)a,(decimal_t*)b,res),
                                   res);
}


inline
int my_decimal_mul(uint32_t mask, db_decimal *res, const db_decimal *a,
                   const db_decimal *b)
{
  return check_result_and_overflow(mask,
                                   decimal_mul((decimal_t*)a,(decimal_t*)b,res),
                                   res);
}


inline
int my_decimal_div(uint32_t mask, db_decimal *res, const db_decimal *a,
                   const db_decimal *b, int div_scale_inc)
{
  return check_result_and_overflow(mask,
                                   decimal_div((decimal_t*)a,(decimal_t*)b,res,
                                               div_scale_inc),
                                   res);
}


inline
int my_decimal_mod(uint32_t mask, db_decimal *res, const db_decimal *a,
                  const db_decimal *b)
{
  return check_result_and_overflow(mask,
                                   decimal_mod((decimal_t*)a,(decimal_t*)b,res),
                                   res);
}


/**
  @return
    -1 if a<b, 1 if a>b and 0 if a==b
*/
inline
int my_decimal_cmp(const db_decimal *a, const db_decimal *b)
{
  return decimal_cmp((decimal_t*) a, (decimal_t*) b);
}


inline
int my_decimal_intg(const db_decimal *a)
{
  return decimal_intg((decimal_t*) a);
}


void my_decimal_trim(uint32_t *precision, uint32_t *scale);

#define decimal_trim   my_decimal_trim


#endif /*__decimal_h__*/

